package com.swak.consistency.core.invoke;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import com.swak.App;
import com.swak.asm.MethodCache;
import com.swak.asm.MethodCache.MethodMeta;
import com.swak.asm.Wrapper;
import com.swak.consistency.core.aspect.ConsistencyProceeding;
import com.swak.consistency.core.invoke.impl.FieldInvocation;
import com.swak.consistency.core.invoke.impl.MethodInvocation;
import com.swak.consistency.core.proxy.ConsistencyProxy;
import com.swak.utils.AopTargetUtils;
import com.swak.utils.StringUtils;

/**
 * 这是默认的执行器
 * 
 * @author DELL
 */
@SuppressWarnings("rawtypes")
public class JavassistInvoker implements Invoker {

	@Override
	public Invocation createInvocation(ConsistencyProceeding proceeding, String targetMethod) {
		if (proceeding.getDeclareField() != null) {
			return this.createFieldInvocation(proceeding);
		}
		return this.createMethodInvocation(proceeding, targetMethod);
	}

	private MethodInvocation createMethodInvocation(ConsistencyProceeding proceeding, String targetMethod) {
		MethodInvocation invocation = new MethodInvocation();
		invocation.setClazz(proceeding.getTarget());
		invocation.setMethod(this.buildInvocationMethod(proceeding, targetMethod));
		invocation.setArgs(proceeding.getArgs());
		return invocation;
	}

	private FieldInvocation createFieldInvocation(ConsistencyProceeding proceeding) {
		FieldInvocation invocation = new FieldInvocation();
		invocation.setClazz(proceeding.getTarget());
		invocation.setMethod(this.buildInvocationMethod(proceeding, null));
		invocation.setArgs(proceeding.getArgs());
		invocation.setDeclareClass(proceeding.getDeclareClass());
		invocation.setField(proceeding.getDeclareField().getName());
		return invocation;
	}

	private String buildInvocationMethod(ConsistencyProceeding proceeding, String targetMethod) {

		// 创建执行类代理
		Wrapper.getWrapper(proceeding.getTarget());

		// 方法转换
		Method method = proceeding.getMethod();
		MethodMeta methodMeta = MethodCache.get(proceeding.getTarget()).lookup(method);
		String invocationMethodName = methodMeta.getMethodDesc();
		if (targetMethod != null) {
			invocationMethodName = targetMethod + StringUtils.removeStart(methodMeta.getMethodDesc(), method.getName());
		}
		return invocationMethodName;
	}

	@Override
	public Object invokeInvocation(Invocation invocation) throws Exception {
		if (invocation instanceof FieldInvocation) {
			return this.doInvokeFieldInvocation((FieldInvocation) invocation);
		}
		return this.doInvokeMethodInvocation((MethodInvocation) invocation, null);
	}

	private Object doInvokeFieldInvocation(FieldInvocation invocation) throws Exception {
		Class<?> declareClass = invocation.getDeclareClass();
		Field[] fields = declareClass.getFields();
		Field declareField = null;
		for (Field field : fields) {
			if (field.getClass() == invocation.getClazz() && field.getName().equals(invocation.getField())) {
				declareField = field;
				break;
			}
		}
		Object bean = App.getBean(declareClass);
		Object actualBean = AopTargetUtils.getTarget(bean);
		Object service = declareField.get(actualBean);

		Object actualService = service;
		if (service instanceof ConsistencyProxy) {
			actualService = ((ConsistencyProxy) service).getActualService();
		}
		return this.doInvokeMethodInvocation(invocation, actualService);
	}

	private Object doInvokeMethodInvocation(MethodInvocation invocation, Object service) throws Exception {
		Class<?> clazz = invocation.getClazz();
		String method = invocation.getMethod();
		Object[] args = invocation.getArgs();
		Object actualService = service == null ? App.getBean(clazz) : service;
		return Wrapper.getWrapper(clazz).invokeMethod(actualService, method, args);
	}
}
